﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Drawing;

namespace System.Windows.Forms.ButtonInternal;

internal abstract class CheckBoxBaseAdapter : CheckableControlBaseAdapter
{
    protected const int FlatCheckSize = 11;

    [ThreadStatic]
    private static Bitmap? t_checkImageChecked;
    [ThreadStatic]
    private static Color t_checkImageCheckedBackColor;

    [ThreadStatic]
    private static Bitmap? t_checkImageIndeterminate;
    [ThreadStatic]
    private static Color t_checkImageIndeterminateBackColor;

    internal CheckBoxBaseAdapter(ButtonBase control) : base(control)
    {
    }

    protected new CheckBox Control => (CheckBox)base.Control;

    protected void DrawCheckFlat(
        PaintEventArgs e,
        LayoutData layout,
        Color checkColor,
        Color checkBackground,
        Color checkBorder,
        ColorData colors)
    {
        Rectangle bounds = layout.CheckBounds;

        // Removed subtracting one for Width and Height. In VS 2003 (Everett) we needed to do this, as we were using
        // GDI+ to draw the border. Now that we are using GDI, this is unnecessary.

        if (!layout.Options.DotNetOneButtonCompat)
        {
            bounds.Width--;
            bounds.Height--;
        }

        using (DeviceContextHdcScope hdc = new(e))
        {
            using CreatePenScope hpen = new(checkBorder);
            hdc.DrawRectangle(bounds, hpen);

            // Now subtract, since the rest of the code is like Everett.
            if (layout.Options.DotNetOneButtonCompat)
            {
                bounds.Width--;
                bounds.Height--;
            }

            bounds.Inflate(-1, -1);
        }

        if (Control.CheckState == CheckState.Indeterminate)
        {
            bounds.Width++;
            bounds.Height++;
            DrawDitheredFill(e.Graphics, colors.ButtonFace, checkBackground, bounds);
        }
        else
        {
            using DeviceContextHdcScope hdc = new(e);
            using CreateBrushScope hbrush = new(checkBackground);

            // Even though we are using GDI here as opposed to GDI+ in VS 2003 (Everett), we still need to add 1.
            bounds.Width++;
            bounds.Height++;
            hdc.FillRectangle(bounds, hbrush);
        }

        DrawCheckOnly(e, layout, colors, checkColor);
    }

    internal static void DrawCheckBackground(
        bool controlEnabled,
        CheckState controlCheckState,
        IDeviceContext deviceContext,
        Rectangle bounds,
        Color checkBackground,
        bool disabledColors)
    {
        using DeviceContextHdcScope hdc = deviceContext.ToHdcScope();

        Color color;

        if (!controlEnabled && disabledColors)
        {
            color = SystemColors.Control;
        }
        else if (controlCheckState == CheckState.Indeterminate && checkBackground == SystemColors.Window && disabledColors)
        {
            Color comboColor = SystemInformation.HighContrast ? SystemColors.ControlDark : SystemColors.Control;
            color = Color.FromArgb(
                (byte)((comboColor.R + SystemColors.Window.R) / 2),
                (byte)((comboColor.G + SystemColors.Window.G) / 2),
                (byte)((comboColor.B + SystemColors.Window.B) / 2));
        }
        else
        {
            color = checkBackground;
        }

        using CreateBrushScope hbrush = new(color);

        RECT rect = bounds;
        PInvoke.FillRect(hdc, rect, hbrush);
    }

    protected void DrawCheckBackground(
        PaintEventArgs e,
        Rectangle bounds,
        Color checkBackground,
        bool disabledColors,
        ColorData colors)
    {
        // Area behind check

        if (Control.CheckState == CheckState.Indeterminate)
        {
            DrawDitheredFill(e.GraphicsInternal, colors.ButtonFace, checkBackground, bounds);
        }
        else
        {
            DrawCheckBackground(Control.Enabled, Control.CheckState, e, bounds, checkBackground, disabledColors);
        }
    }

    protected void DrawCheckOnly(PaintEventArgs e, LayoutData layout, ColorData colors, Color checkColor) =>
        DrawCheckOnly(
            FlatCheckSize,
            Control.Checked,
            Control.Enabled,
            Control.CheckState,
            e.GraphicsInternal,
            layout,
            colors,
            checkColor);

    internal static void DrawCheckOnly(
        int checkSize,
        bool controlChecked,
        bool controlEnabled,
        CheckState controlCheckState,
        Graphics g,
        LayoutData layout,
        ColorData colors,
        Color checkColor)
    {
        if (!controlChecked)
        {
            return;
        }

        if (!controlEnabled)
        {
            checkColor = colors.ButtonShadow;
        }
        else if (controlCheckState == CheckState.Indeterminate)
        {
            checkColor = SystemInformation.HighContrast ? colors.Highlight : colors.ButtonShadow;
        }

        Rectangle fullSize = layout.CheckBounds;

        if (fullSize.Width == checkSize)
        {
            fullSize.Width++;
            fullSize.Height++;
        }

        fullSize.Width++;

        fullSize.Height++;
        Bitmap checkImage;

        if (controlCheckState == CheckState.Checked)
        {
            checkImage = GetCheckBoxImage(checkColor, fullSize, ref t_checkImageCheckedBackColor, ref t_checkImageChecked);
        }
        else
        {
            Debug.Assert(
                controlCheckState == CheckState.Indeterminate,
                "we want to paint the check box only if the item is checked or indeterminate");
            checkImage = GetCheckBoxImage(checkColor, fullSize, ref t_checkImageIndeterminateBackColor, ref t_checkImageIndeterminate);
        }

        fullSize.Y -= layout.Options.DotNetOneButtonCompat ? 1 : 2;

        ControlPaint.DrawImageColorized(g, checkImage, fullSize, checkColor);
    }

    internal static Rectangle DrawPopupBorder(Graphics g, Rectangle r, ColorData colors)
    {
        using DeviceContextHdcScope hdc = new(g);
        return DrawPopupBorder(hdc, r, colors);
    }

    internal static Rectangle DrawPopupBorder(PaintEventArgs e, Rectangle r, ColorData colors)
    {
        using DeviceContextHdcScope hdc = new(e);
        return DrawPopupBorder(hdc, r, colors);
    }

    internal static Rectangle DrawPopupBorder(HDC hdc, Rectangle r, ColorData colors)
    {
        using CreatePenScope high = new(colors.Highlight);
        using CreatePenScope shadow = new(colors.ButtonShadow);
        using CreatePenScope face = new(colors.ButtonFace);

        hdc.DrawLine(high, r.Right - 1, r.Top, r.Right - 1, r.Bottom);
        hdc.DrawLine(high, r.Left, r.Bottom - 1, r.Right, r.Bottom - 1);

        hdc.DrawLine(shadow, r.Left, r.Top, r.Left, r.Bottom);
        hdc.DrawLine(shadow, r.Left, r.Top, r.Right - 1, r.Top);

        hdc.DrawLine(face, r.Right - 2, r.Top + 1, r.Right - 2, r.Bottom - 1);
        hdc.DrawLine(face, r.Left + 1, r.Bottom - 2, r.Right - 1, r.Bottom - 2);

        r.Inflate(-1, -1);
        return r;
    }

    protected ButtonState GetState()
    {
        ButtonState style = 0;

        if (Control.CheckState == CheckState.Unchecked)
        {
            style |= ButtonState.Normal;
        }
        else
        {
            style |= ButtonState.Checked;
        }

        if (!Control.Enabled)
        {
            style |= ButtonState.Inactive;
        }

        if (Control.MouseIsDown)
        {
            style |= ButtonState.Pushed;
        }

        return style;
    }

    protected void DrawCheckBox(PaintEventArgs e, LayoutData layout)
    {
        ButtonState style = GetState();

        if (Control.CheckState == CheckState.Indeterminate)
        {
            if (Application.RenderWithVisualStyles)
            {
                CheckBoxRenderer.DrawCheckBoxWithVisualStyles(
                    e,
                    new Point(layout.CheckBounds.Left, layout.CheckBounds.Top),
                    CheckBoxRenderer.ConvertFromButtonState(style, isMixed: true, Control.MouseIsOver),
                    Control.HWNDInternal);
            }
            else
            {
                ControlPaint.DrawMixedCheckBox(e.GraphicsInternal, layout.CheckBounds, style);
            }
        }
        else
        {
            if (Application.RenderWithVisualStyles)
            {
                CheckBoxRenderer.DrawCheckBoxWithVisualStyles(
                    e,
                    new Point(layout.CheckBounds.Left, layout.CheckBounds.Top),
                    CheckBoxRenderer.ConvertFromButtonState(style, isMixed: false, Control.MouseIsOver),
                    Control.HWNDInternal);
            }
            else
            {
                ControlPaint.DrawCheckBox(e.GraphicsInternal, layout.CheckBounds, style);
            }
        }
    }

    private static Bitmap GetCheckBoxImage(Color checkColor, Rectangle fullSize, ref Color cacheCheckColor, ref Bitmap? cacheCheckImage)
    {
        if (cacheCheckImage is not null
            && cacheCheckColor.Equals(checkColor)
            && cacheCheckImage.Width == fullSize.Width
            && cacheCheckImage.Height == fullSize.Height)
        {
            return cacheCheckImage;
        }

        cacheCheckImage?.Dispose();

        // We draw the checkmark slightly off center to eliminate 3-D border artifacts and compensate below
        RECT rcCheck = new Rectangle(0, 0, fullSize.Width, fullSize.Height);
        Bitmap bitmap = new(fullSize.Width, fullSize.Height);

        using (Graphics offscreen = Graphics.FromImage(bitmap))
        {
            offscreen.Clear(Color.Transparent);
            using DeviceContextHdcScope hdc = new(offscreen, applyGraphicsState: false);
            PInvoke.DrawFrameControl(
                hdc,
                ref rcCheck,
                DFC_TYPE.DFC_MENU,
                DFCS_STATE.DFCS_MENUCHECK);
        }

        bitmap.MakeTransparent();
        cacheCheckImage = bitmap;
        cacheCheckColor = checkColor;

        return cacheCheckImage;
    }

    protected void AdjustFocusRectangle(LayoutData layout)
    {
        if (string.IsNullOrEmpty(Control.Text))
        {
            // When a CheckBox has no text, AutoSize sets the size to zero and thus there's no place around which
            // to draw the focus rectangle. So, when AutoSize == true we want the focus rectangle to be rendered
            // inside the box. Otherwise, it should encircle all the available space next to the box (like it's
            // done in WPF and ComCtl32).
            layout.Focus = Control.AutoSize ? Rectangle.Inflate(layout.CheckBounds, -2, -2) : layout.Field;
        }
    }

    internal override LayoutOptions CommonLayout()
    {
        LayoutOptions layout = base.CommonLayout();
        layout.CheckAlign = Control.CheckAlign;
        layout.TextOffset = false;
        layout.ShadowedText = !Control.Enabled;
        layout.LayoutRTL = Control.RightToLeft == RightToLeft.Yes;

        return layout;
    }
}
